package com.example.javadocsearcher.common;

import com.example.javadocsearcher.util.Index;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import org.nlpcn.commons.lang.occurrence.Count;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created with IntelliJ IDEA 2023.
 * Description:
 * User: homechen
 * Date: 2024-03-03
 * Time: 13:30
 */
public class Parser {

    private Index index = new Index(); // 导入制作索引模块
    // 指定 api 文档的目录
    private static final String INPUT_PATH = "D:/edge install/Java API/docs/api";

    /**
     * 解决在电脑重启后，加载索引慢的问题
     * 使用多线程中累计多个线程一起执行的时间的原子类，基于 CAS
     */
    private AtomicLong t1 = new AtomicLong(0);
    private AtomicLong t2 = new AtomicLong(0);

    // Parser 类的入口
    public void Run() throws InterruptedException {
        long begin = System.currentTimeMillis();
        System.out.println("索引制作开始！");

        // 枚举出该目录下面的所有的 html 文件，都存储在 fileList 中
        List<File> fileList = new ArrayList<>();
        enumFile(INPUT_PATH, fileList);
        long enumFile = System.currentTimeMillis();
        System.out.println("遍历文件的时间：Time：" + (enumFile - begin) + " ms");
        //  int count = 0;
        // 使用线程池进行优化解析文件 + 制作索引过程   但是这里的线程数量的设置根据自己的电脑配置，这里是CPU密集型 n-1 合适（n为核心线程）
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        CountDownLatch downLatch = new CountDownLatch(fileList.size());// 撞线个数等于处理的文件个数
        for (File file : fileList) {
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("开始解析：" + file.getAbsolutePath());
                    //  count++;  线程池中不能使用改变的量，会造成线程安全问题
                    // 读取每个 html 文件的内容，并开始构建索引 -----<
                    parseHtml(file);
                    downLatch.countDown(); // 遍历完成一个就减少一个
                }
            });
        }
        //  但是可能线程池中的任务还没有全部执行完，就执行了保存索引操作，这里必须阻塞等待线程池工作完成
        downLatch.await(); // 等待线程池完成
        // 解决因为线程池还在等待任务导致进程不退出问题，因为默认创建的线程是非守护线程
        threadPool.shutdown();
        long parseFile = System.currentTimeMillis();
        System.out.println("解析文件 + 制作索引 Time：" + (parseFile - begin) + " ms"); // 发现解析文件 + 制作索引消耗的时间消耗很大，使用线程池进行优化
        // TODO 将内存中保存到索引数据结构，保存到指定文件中
        index.save();  // 保存到磁盘中

        long end = System.currentTimeMillis();
        System.out.println("索引制作完成，Time：" + (end - begin) + " ms");
        System.out.println("t1：" + t1 + " | t2：" + t2);
    }

    private void enumFile(String rootPath, List<File> fileList) {
        File rootFile = new File(rootPath);
        // 列举出 rootFile 下面有哪些文件 / 目录
        File[] files = rootFile.listFiles();
        for (File file : files) {
            // 是一个目录就继续递归 ，不是就判断是否为 .html 为后缀 是就添加到 list 中
            if (file.isDirectory()) {
                enumFile(file.getAbsolutePath(), fileList);
            } else {
                if (file.getAbsolutePath().endsWith(".html")) {
                    fileList.add(file);
                }
            }
        }
    }

    private void parseHtml(File file) {
        // 解析出文件的标题
        String title = parseTitle(file);
        // 解析出文件的 url
        String url = parseUrl(file);
        long begin = System.nanoTime();// 使用微秒进行统计
        // 解析出文件的 正文
        //  String content = parseContent(file);

        // 正则表达式的解析
        String content = parseContentByRegex(file);
        long mid = System.nanoTime();
        // TODO 将解析出来的信息添加到索引中  即 Index 类
        index.addDoc(title, url, content); // 使用线程池处理线程安全问题，多线程修改同一变量
        long end = System.nanoTime();
        t1.addAndGet(mid - begin);
        t2.addAndGet(end - begin);
    }

    /**
     * 将正文转为行文本格式，通过设置标志位的方式  为 true 就进行凭借，false 就进行 continue
     * 1、遇到  < 将标志位设为 false，遇到  > 将标志位设置为 true
     * 2、并去掉回车、换行等字符
     * 3、使用字符流进行读取
     */
    private String parseContent(File file) {
        // 这里会出现首次制作索引加载速度太慢现象，首次加载是在磁盘中，后面加载就会直接从内存取数据
        // 使用 BufferedReader 来进行处理这种情况，它内置一个缓冲区，会自动将FileReader中的内容加载到内存中

        // try (FileReader fileReader = new FileReader(file)) {
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file), 1024 * 1024)) {  // 直接将缓冲区的大小设置为 1M
            boolean isContent = true;
            StringBuilder stringBuilder = new StringBuilder();
            while (true) {
                // int ch = fileReader.read();//读取一个字符
                int ch = bufferedReader.read(); // 读取一个字符
                if (ch == -1) {
                    break;//此时为文件末尾
                }
                char c = (char) ch;
                if (isContent) { // 此时为 正常内容
                    if (c == '<') {
                        isContent = false;
                        continue;
                    }
                    // 进行优化，转化为空格即可
                    if (c == '\n' || c == '\r') {
                        c = ' ';
                    }
                    stringBuilder.append(c);
                } else {
                    // 此时还在非正常内容
                    if (c == '>') {
                        isContent = true;
                    }
                }
            }
            return stringBuilder.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 对文档解析正文操作进行改进，通过正则表达式去掉里面的JS代码
     * . -> 表示非换行字符（不包括 \n \r）
     * * -> 表示匹配一次或者多次
     * .* -> 匹配非换行字符一次或者多次
     * ? -> 非贪婪匹配，只匹配一个符合条件的最短结果
     * \s -> 表示匹配任意空格字符  \n \t 空格 等
     */
    public String parseContentByRegex(File file) {
        // 先将正文部分读取出来，不去掉里面的标签内容
        String content = readFile(file);
        // 先去除里面的 script 代码
        content = content.replaceAll("<script.*?>(.*?)</script>", " ");  // 之后只剩下，其他标签 + 正文
        // 去除其他普通标签
        content = content.replaceAll("<.*?>", " ");
        // 将多个空格合并成一个空格
        content = content.replaceAll("\\s+", " "); // 子表达式一次或者多次  -》 +
        return content;
    }

    private String readFile(File file) {
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file), 1024 * 1024)) {
            StringBuilder stringBuilder = new StringBuilder();
            while (true) {
                int ch = bufferedReader.read();
                if (ch == -1) {
                    // 读到文件末尾
                    break;
                }
                char c = (char) ch;
                // 对换行 | 回车进行处理
                if (c == '\n' || c == '\r') {
                    c = ' ';
                }
                stringBuilder.append(c);
            }
            return stringBuilder.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 线上的 url：https://docs.oracle.com/javase/8/docs/api/java/io/BufferedReader.html
     * 本地文件的路径地址：D:/edge%20install/Java%20API/docs/api/java/io/BufferedReader.html
     * 可以拿着本地url截取后缀与线上 url进行拼接，就可以得到线上文档路径了
     */
    private String parseUrl(File file) {
        String str1 = "https://docs.oracle.com/javase/8/docs/api";
        String str2 = file.getAbsolutePath().substring(INPUT_PATH.length()); // 上面已经有了文件的根路径了
        return str1 + str2; // 先不匹配正则表达式
    }

    /**
     * 直接以文件名作为标题即可  abc.html  ->  abc
     */
    private String parseTitle(File file) {
        String name = file.getName();
        return name.substring(0, name.length() - ".html".length());
    }

    //  测试索引制作 + 将索引保存到本地磁盘中
//    public static void main(String[] args) throws InterruptedException {
//        Parser parser = new Parser();
//        parser.Run();
//    }
}
